<?php

# -- DÉBUT NOTE DE LICENCE -------------------------------------
#
# Ce fichier fait partie de Clicpage.
#
# Copyright (c) 2010 Francois (www.clicpage.net)
# Distribué selon les termes de la licence GPL version 2.0.
# Consultez le fichier LICENCE ou
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# -- FIN NOTE DE LICENCE ---------------------------------------

/**
@defgroup CP_CORE	Coeur de Clicpage
*/

/**
@ingroup CP_CORE
@nosubgrouping
@brief Coeur de Clicpage

cpCore est le coeur de Clicpage. Il gère l'authentification, la base de données,
les plugins et donne accès à une instance de cpSite.
*/
class cpCore
{
	
	
	
	
	
	public $con;				///< <b>connection</b>	Connexion à la base de données
	public $prefix;				///< <b>string</b>		Préfixe des tables de Clicpage
	
	protected $langue;			///< <b>string</b>		Langue utilisée pour l'affichage
	
	protected $comportements = array();			///< <b>array</b>		Comportements
	
	public $plugins = array();					///< <b>array</b>		Plugins
	public $generateurs = array();		///< <b>array</b>		Générateurs de page
	public $gadgets = array();					///< <b>array</b>		Gadgets
	public $filtresAntispam = array();			///< <b>array</b>		Filtres antispam
	
	public $modulesAdmin;		///< <b>array</b>	Modules d'administration
	public $servicesAdmin;		///< <b>array</b>	Services pour l'administration
	
	public $themes = array();					///< <b>array</b>		Thèmes
	
	public $langues = array();					///< <b>array</b>		Langues

	public $auth;				///< <b>cpAuth</b>		Instance de cpAuth
	public $site;				///< <b>cpSite</b>		Instance de cpSite
	public $public;				///< <b>cpPublic</b>	Instance de cpPublic
	
	public $session;			///< <b>sessionDB</b>	Instance de sessionDB (Clearbricks)
	
	# Variables pour l'inclusion des fichiers plugins
	protected static $superglobals = array('GLOBALS','_SERVER','_GET','_POST','_COOKIE','_FILES','_ENV','_REQUEST','_SESSION');
	protected static $_k;
	protected static $_n;
	
	
	
	

	/// @name Initialisation
	//@{
	
	
	/**
	Initialise tout ce qui est nécessaire pour faire fonctionner Clicpage.
	Prend comme paramètres les informations de connexion à la base de données.
	
	@param	driver		<b>string</b>	Type de base de données (mysql, pgsql ou sqlite)
	@param	host		<b>string</b>	Hôte de la base de données
	@param	db			<b>string</b>	Nom de la base de données
	@param	user		<b>string</b>	Nom d'utilisateur pour l'accès à la base de données
	@param	password	<b>string</b>	Mot de passe pour l'accès à la base de données
	@param	prefix		<b>string</b>	Préfixe des tables
	@param	persist		<b>boolean</b>	Connexion persistente à la base de données
	*/
	public function __construct($driver, $host, $db, $user, $password, $prefix, $persist)
	{
		$this->con = dbLayer::init($driver,$host,$db,$user,$password,$persist);
	
		# define weak_locks for mysql
		if ($this->con instanceof mysqlConnection) {
			mysqlConnection::$weak_locks = true;
		}
		
		$this->prefix = $prefix;

		$this->initSite();
		
	}
	
	
	/** @ignore */
	public function initSession()
	{
		if(!is_object($this->session))	{
			$this->session = new sessionDB($this->con,$this->prefix.'session',CP_NOM_SESSION,null,null,false);
		}
	}
	
	
	/** @ignore */
	public function initAuth()
	{
		$this->initSession();
		if(!is_object($this->auth))	{
			$this->auth = new cpAuth($this);
		}
	}
	
	
	/** @ignore */
	protected function initSite()
	{
		if(!is_object($this->site)) {
			$this->site = new cpSite($this);
		}
	}
	
	
	/**
	Crée une instance de cpPublic, accessible via <var>$core->public</var>.
	*/
	public function initPublic()
	{
		if(!is_object($this->public)) {
			$this->public = new cpPublic($this);
		}
	}
	//@}
	
	
	
	
	
	/// @name Versions
	//@{
	
	
	/**
	Retourne la version d'un composant de Clicpage.
	
	Note: Pour obtenir la version d'un plugin, <var>$type</var> doit
	être 'plugin' et <var>$module</var> l'identifiant du plugin.
	
	@param	type	<b>string</b>	Type de composant (core, plugin)
	@param	module	<b>string</b>	Identifiant du composant
	
	@return	<b>string</b>	Version
	*/
	public function getVersion($type,$composant)
	{
		
		$rs = $this->con->select(
			'SELECT version '.
			'FROM '.$this->prefix.'version '.
			"WHERE type = '".$this->con->escape($type)."' AND composant = '".$this->con->escape($composant)."'"
		);
		
		if($rs->isEmpty()) {
			return null;
		}
		
		return $rs->version;
		
	}
	
	
	/**
	Définit la version d'un composant de Clicpage.
	
	@param	type	<b>string</b>	Type de composant (core, plugin)
	@param	module	<b>string</b>	Identifiant du composant
	
	@return	<b>string</b>	Version
	*/
	public function setVersion($type,$composant,$version)
	{
		$check = $this->getVersion($type, $composant);
		
		$cur = $this->con->openCursor($this->prefix.'version');
		$cur->composant	= $composant;
		$cur->type		= $type;
		$cur->version	= $version;
		
		if(is_null($check)) {
			$cur->insert();
		} else {
			$cur->update("WHERE type = '".$this->con->escape($type)."' AND composant = '".$this->con->escape($composant)."' LIMIT 1");
		}
	}
	//@}
	
	
	

	
	/// @name Comportements
	//@{
	
	
	/**
	Ajoute un nouveau comportement à la pile de comportements. <var>$func</var>
	doit être une fonction de rappel (callback) valide.
	
	@param	comportement	<b>string</b>		Nom du comportement
	@param	func			<b>callback</b>		Fonction à appeler
	*/
	public function ajouterComportement($comportement,$func)
	{
		if (is_callable($func)) {
			$this->comportements[$comportement][] = $func;
		}
	}
	
	
	/**
	Vérifie si des fonctions sont associées au comportement
	<var>$comportement</var>.

	@param	comportement	<b>string</b>	Nom du comportement
	
	@return	<b>boolean</b>
	*/
	public function comportementExiste($comportement)
	{
		return isset($this->comportements[$comportement]);
	}
	
	
	/**
	Retourne la pile de comportements (ou une partie de celle-ci).

	@param	comportement	<b>string</b>		Nom du comportement
	
	@return	<b>array</b>
	*/
	public function getComportements($comportement=null)
	{
		if (empty($this->comportements)) return null;

		if (empty($comportement)) {
			return $this->comportements;
		} elseif (isset($this->comportements[$comportement])) {
			return $this->comportements[$comportement];
		}
		
		return array();
	}
	
	
	/**
	Appelle toutes les fonctions de la pile de comportements pour un comportement
	donné, et retourne le résultat concaténé de chaque fonction.
	
	Les paramètres ajoutés après <var>$comportement</var> seront utilisés
	lors de l'appel des fonctions de la pile de comportements.
	
	@param	comportement	<b>string</b>	Nom du comportement
	
	@return	<b>string</b>	Résultat concaténé du comportement
	*/
	public function executerComportement($comportement)
	{
		
		if (isset($this->comportements[$comportement]))
		{
			$args = func_get_args();
			array_shift($args);
			
			$res = '';
			
			foreach ($this->comportements[$comportement] as $f) {
				$res .= call_user_func_array($f,$args);
			}
			
			return $res;
		}
	}
	//@}
	
	
	
	

	/// @name Plugins
	//@{
	
	
	/**
	Charge les fichiers _prepend.php des plugins.
	<var>$contexte</var> peut prendres les valeurs suivantes:
	- admin: les fichiers _admin.php des plugins seront également chargés
	- public: les fichiers _public.php des plugins seront également chargés
	
	@param	contexte	<b>string</b>		'admin' ou 'public'
	*/
	public function chargerPlugins($contexte = null)
	{
		
		$dossierPlugins = path::real(CP_CHEMIN_PLUGINS.'/');
		
		if (($d = @dir($dossierPlugins)) === false) {
			return false;
		}
		
		$plugins = $this->con->select(
			'SELECT composant,version '.
			'FROM '.$this->prefix.'version '.
			"WHERE type = 'plugin'");
		
		while($plugins->fetch())
		{
			
			$dossierPlugin = $dossierPlugins.'/'.$plugins->composant;
			
			if(is_dir($dossierPlugin) && 
			   file_exists($dossierPlugin.'/description.php') &&
			   !file_exists($dossierPlugin.'/_desactive') )
			{	
				$this->dossier		= $dossierPlugin;
				$this->identifiant	= $plugins->composant;
				$this->version		= $plugins->version;
				require $dossierPlugin.'/description.php';
				$this->dossier		= NULL;
				$this->identifiant	= NULL;
				$this->version		= NULL;
			}
			
		}
		
		# Ordonner les plugins
		uasort($this->plugins,array($this,'ordonnerPlugins'));

		# Inclusion des fichiers _prepend.php, _admin.php et _public.php
		foreach ($this->plugins as $id => $m)
		{
			$this->chargerFichierPlugin($m['chemin'].'/_prepend.php');
			
			if($contexte == 'admin') {
				$this->chargerFichierPlugin($m['chemin'].'/_admin.php');
			} elseif($contexte == 'public') {
				$this->chargerFichierPlugin($m['chemin'].'/_public.php');
			}
		}
		
	}
	
	
	/** @ignore */
	protected function ordonnerPlugins($a,$b)
	{
		if ($a['priorite'] == $b['priorite']) {
			return strcasecmp($a['nom'],$b['nom']);
		}
		
		return ($a['priorite'] < $b['priorite']) ? -1 : 1;
	}
	
	
	/** @ignore */
	protected function chargerFichierPlugin($________)
	{
		if (!file_exists($________)) {
			return;
		}
		
		self::$_k = array_keys($GLOBALS);
		
		foreach (self::$_k as self::$_n) {
			if (!in_array(self::$_n,self::$superglobals)) {
				global ${self::$_n};
			}
		}
		
		return require $________;
	}
	
	
	/**
	Initialiser un plugin. Doit être appelé dans le fichier
	description.php de chaque plugin.
	
	<var>$priority</var> est un entier. Les plugins sont chargés par 
	ordre croissant de priorité, puis de nom.
	
	@param	nom		<b>string</b>	Nom du plugin
	@param	description	<b>string</b>	Description du plugin
	@param	auteur		<b>string</b>	Auteur
	@param	version		<b>string</b>	Version du plugin
	@param	priorite	<b>string</b>	Priorité du plugin
	*/
	public function initialiserPlugin($nom, $description, $auteur, $version, $priorite, $versionCp)
	{
		$this->plugins[$this->identifiant] = array(
			'chemin' 		=> $this->dossier,
			'identifiant' 	=> $this->identifiant,
			'nom' 			=> $nom,
			'description' 	=> $description,
			'auteur' 		=> $auteur,
			'version' 		=> $version,
			'versionDB'		=> $this->version,
			'priorite' 		=> $priorite === null ? 1000 : (integer) $priorite,
			'versionCp'		=> $versionCp
		);
	}
	
	
	/**
	Installe les plugins présents dans le répertoire de plugins de
	Clicpage mais non référencés dans la base de données, en exécutant
	les fichiers _installer.php de ceux-ci.
	*/
	public function installerPlugins()
	{
		
		$res = array(
			'succes'	=> array(),
			'echec'		=> array()
		);
		
		$dossierPlugins = path::real(CP_CHEMIN_PLUGINS.'/');
		
		if (($d = @dir($dossierPlugins)) === false) {
			return false;
		}
		
		while (($idPlugin = $d->read()) !== false)
		{
			
			$dossierPlugin = $dossierPlugins.'/'.$idPlugin;
			
			if ($idPlugin != '.' && $idPlugin != '..' && is_dir($dossierPlugin)
				&& file_exists($dossierPlugin.'/description.php')
				&& !file_exists($dossierPlugin.'/_desactive') )
			{
				
				$msg = '';
					
				# Inclusion du fichier de description
				$this->dossier = null;
				$this->identifiant = 'temp';
				$this->version = null;
				$this->chargerFichierPlugin($dossierPlugin.'/description.php');
				
				if(is_array($this->plugins['temp']['versionCp']) && (version_compare($this->plugins['temp']['versionCp'][0], CP_VERSION,'>') || version_compare($this->plugins['temp']['versionCp'][1], CP_VERSION,'<')))
				{
					$i = false;
					$msg = sprintf(__('Le module %s nécessite une version de Clicpage comprise entre %s et %s pour fonctionner adéquatement.'),
						$idPlugin, $this->plugins['temp']['versionCp'][0], $this->plugins['temp']['versionCp'][1]);
				}
				elseif(!empty($this->plugins['temp']['versionCp']) && version_compare($this->plugins['temp']['versionCp'], CP_VERSION,'>'))
				{
					$i = false;
					$msg = sprintf(__('Le module %s nécessite Clicpage %s ou ultérieur pour fonctionner adéquatement.'),
						$idPlugin, $this->plugins['temp']['versionCp']);
				}
				else
				{
					# Inclusion du fichier d'installation
					$i = $this->installerPlugin($dossierPlugin, $msg);
				}
					
				# Résultat de l'installation
				if($i === true) {
					$res['succes'][] = $idPlugin;
				} elseif($i === false) {
					$res['echec'][] = array(
						$idPlugin,
						$msg
					);
				}

			}
			
		}
		
		$this->plugins = array();
		
		$d->close();
		
		return $res;
		
	}
	
	
	/** @ignore */
	protected function installerPlugin($dossierPlugin, &$msg)
	{
		try {
			$i = $this->chargerFichierPlugin($dossierPlugin.'/_installer.php');
			if ($i === true) {
				return true;
			}
		} catch (cpErreur $e) {
			$msg = $e->getMessage();
			return false;
		} catch (Exception $e) {
			$msg = $e->getMessage();
			return false;
		}
		
		return null;
	}
	//@}
	
	
	
	

	/// @name Utilitaires SQL
	//@{
	
	
	/**
	Renvoie une partie de requête SQL correspondant à une
	condition à placer après «WHERE».
	
	@param	champ	<b>string</b>	Nom du champ
	@param	valeurs	<b>mixed</b>	Valeurs
	@param	type	<b>integer</b>	Type de donnée attendu (CP_STRING, CP_INTEGER, CP_BOOL, CP_DATE)
	@param 	table	<b>string</b>	Alias pour le nom de la table
	
	@return	<b>string</b>	Partie de requête SQL
	*/
	public function conditionSql($champ, $valeurs, $type=CP_STRING, $table=null)
	{

		$champ = empty($table) ? $this->con->escapeSystem($champ) : $table.'.'.$this->con->escapeSystem($champ);	
	
		# Date
		if($type == CP_DATE && is_array($valeurs))
		{
			$cond = array();
				
			if(!empty($valeurs['debut'])) {
				$cond[] = $champ." >= '".$this->core->con->escape($valeurs['debut'])."'";
			}
			
			if(!empty($valeurs['fin'])) {
				$cond[] = $champ." <= '".$this->core->con->escape($valeurs['fin'])."'";
			}

			return implode(' '.CP_OPERATEUR_AND.' ', $cond);			
		}
	
		if(is_array($valeurs)) {
			
			$cond = array();
			
			# =
			if(is_array($valeurs['=']) && !empty($valeurs['='])) {
				$bCond = array();
				foreach($valeurs['='] as $v) {
					$bCond[] = "'".$this->con->escape($v)."'";
				}
				$cond[] = $champ.' IN ('.implode(',', $bCond).')';
			} elseif(isset($valeurs['='])) {
				$cond[] = $champ." = '".$this->con->escape($valeurs['='])."'";
			}
			
			# !=
			if(is_array($valeurs['!=']) && !empty($valeurs['!='])) {
				$bCond = array();
				foreach($valeurs['!='] as $v) {
					$bCond[] = "'".$this->con->escape($v)."'";
				}
				$cond[] = $champ.' NOT IN ('.implode(',', $bCond).')';
			}  elseif(isset($valeurs['!='])) {
				$cond[] = $champ." != '".$this->con->escape($valeurs['!='])."'";
			}
			
			# LIKE
			if(is_array($valeurs['like']) && !empty($valeurs['like'])) {
				$bCond = array();
				$op = isset($valeurs['operateurLike']) ? $valeurs['operateurLike'] : CP_OPERATEUR_AND;
				foreach($valeurs['like'] as $v) {
					$bCond[] = $champ." LIKE '".$this->con->escape($v)."'";
				}
				$cond[] = implode(' '.$op.' ', $bCond);
			}  elseif(isset($valeurs['like'])) {
				$cond[] = $champ." LIKE '".$this->con->escape($valeurs['like'])."'";
			}
			
			# NOT LIKE
			if(is_array($valeurs['not_like']) && !empty($valeurs['not_like'])) {
				$bCond = array();
				$op = isset($valeurs['operateurNotLike']) ? $valeurs['operateurNotLike'] : CP_OPERATEUR_AND;
				foreach($valeurs['not_like'] as $v) {
					$bCond[] = $champ." NOT LIKE '".$this->con->escape($v)."'";
				}
				$cond[] = implode(' '.$op.' ', $bCond);
			}  elseif(isset($valeurs['not_like'])) {
				$cond[] = $champ." NOT LIKE '".$this->con->escape($valeurs['not_like'])."'";
			}
			
			$operateur = isset($valeurs['operateur']) ? $valeurs['operateur'] : CP_OPERATEUR_AND;
			
			return implode(' '.$operateur.' ', $cond);
			
		}
		
		if($type == CP_INTEGER) {
			return $champ.' = '.(integer) $valeurs;
		} elseif($type == CP_BOOL) {
			return $champ.' = '.(integer) $valeurs;
		}
		
		return $champ." = '".$this->con->escape($valeurs)."'";
		
	}
	
	
	/**
	Renvoie une requête SQL de type SELECT.
	
	@param	champs	<b>array</b>	Champs à retourner et sur lesquels les conditions peuvent être appliquées
	@param	params	<b>array</b>	Paramètres
	@param	from	<b>string</b>	Sera placé après le mot-clé «FROM»
	@param	type	<b>integer</b>	Type de requête (CP_GET_COMPTEUR ou CP_GET_RS)
	
	@return	<b>string</b>	Requête SQL
	*/
	public function selectSql($champs, $params, $from, $type=CP_GET_RS)
	{
		
		if(!is_array($champs)) {
			trigger_error('Le paramètre champ doit être de type <strong>array</strong> lors de l\'appel de cpCore::selectSql.');	
		}

		$count_only = ($type == CP_GET_COMPTEUR) ? true : false;

		
		# Opérateur
		if(empty($params['operateur'])) {
			$params['operateur'] = CP_OPERATEUR_AND;
		} elseif(!in_array($params['operateur'], array(CP_OPERATEUR_AND, CP_OPERATEUR_OR))) {
			trigger_error('Opérateur invalide');
		}
		
		
		# SELECT
		
		$strReq = 'SELECT ';
		
		$champsSql = array();
		$conditions = array();
			
		foreach($champs as $champ) {
			$champsSql[] = !empty($champ[1]) ? $champ[1].'.'.$champ[0] : $champ[0];

			if(isset($params[$champ[0]]) && !array_key_exists($champ[0], $conditions)) {
				$conditions[$champ[0]] = '('.$this->conditionSql($champ[0], $params[$champ[0]], $champ[2], $champ[1]).')';
			}
			
		}
		
		if(!$count_only)
		{				
			$strReq.=implode($champsSql,",\n")."\n";
		}
		else
		{
			if(isset($params['group']) && !empty($params['group'])) {
				$strReq.=$this->con->escape($params['group']).",\n";
			}
			
			$champ = empty($champs[0][1]) ?
					$this->con->escapeSystem($champs[0][0]) :
					$champs[0][1].'.'.$this->con->escapeSystem($champs[0][0]);
			$strReq.='count('.$champ.")\n";
			
		}
		
		
		
		# FROM
		$strReq.='FROM '.$from."\n";
		
		
		
		# WHERE
		if(is_array($params['conditions_supp'])) {
			$conditions = array_merge($conditions, $params['conditions_supp']);
		}

		if(!empty($conditions)) {
			$strReq.='WHERE '.implode(' '.$params['operateur']."\n", $conditions)."\n";
		}
		
		
		
		# GROUP
		if(isset($params['group']) && !empty($params['group'])) {
			$strReq.= 'GROUP BY '.$this->con->escape($params['group'])."\n";
		}
		
		
		
		# ORDER
		if (!$count_only && !empty($params['order'])) {
			$strReq .= 'ORDER BY '.$this->con->escape($params['order'])."\n";
		}
		
		
		
		# LIMIT
		if (!$count_only && !empty($params['limit'])) {
			$strReq.= $this->con->limit($params['limit'])."\n";
		}
		
		return $strReq;
		
	}
	
	
	/**
	Retourne un résultat de base de données. Si <var>$type</var> est
	CP_GET_COMPTEUR ou CP_GET_RS, le résultat est retourné tel quel. Si
	le type est CP_GET_STATIC, chaque rangée de données est associée à
	une instance de <var>$nomClass</var>.
	
	@param	type	<b>integer</b>		CP_GET_COMPTEUR, CP_GET_RS ou CP_GET_STATIC
	@param	rs		<b>recordset</b>	Résultat de la base de données
	@param	nomClass<b>string</b>		Nom de la classe à associer aux résultats
	@param	extra	<b>mixed</b>		Si spécifié, ce paramètre est passé lors de l'initialisation des classes
	
	@return	<b>recordset</b>
	*/
	public function selectReturn($type, $rs, $nomClass, $extra=null)
	{

		switch($type):
		
			case CP_GET_COMPTEUR:
			case CP_GET_RS:
				return $rs;
			break;
		
			case CP_GET_STATIC:
				return new cpStaticRecord($rs, $nomClass, $this, $extra);
			break;
		
		endswitch;
		
	}
	//@}
	
	
	
	

	/// @name Langues
	//@{

	
	/** @ignore */
	protected function chargerLangues()
	{
		if(empty($this->langues))
		{
		
			$dossierLangues = path::real(CP_CHEMIN_LANGUES.'/');
			
			if (($d = @dir($dossierLangues)) === false) {
				return false;
			}
	
			while (($identifiantLangue = $d->read()) !== false)
			{
				$dossierLangue = $dossierLangues.'/'.$identifiantLangue;
				
				if ($identifiantLangue != '.' && $identifiantLangue != '..' && is_dir($dossierLangue)
				    && file_exists($dossierLangue.'/description.php'))
				{
					$this->dossier = $dossierLangue;
					require $dossierLangue.'/description.php';
					$this->dossier = NULL;
				}
			}		
		}	
	}
	
	
	/**
	Initialise une langue. Cette méthode doit être appelée dans le
	fichier 'description.php' de chaque langue.
	*/
	public function initialiserLangue($identifiant, $nom, $auteur, $version)
	{
		$this->langues[$identifiant] = array(
			'identifiant' => $identifiant,
			'dossier' => $this->dossier,
			'nom' => $nom,
			'auteur' => $auteur,
			'version' => $version
		);
	}
	
	
	/**
	Retourne la langue utilisée pour l'affichage.
	
	@return	<b>string</b>	Code de la langue utilisée pour l'affichage (fr, en, ...)
	*/
	public function getLangue()
	{
		return $this->langue;
	}
	
	
	/**
	Définit la langue à utiliser pour l'affichage.
	
	@param	langue	<b>string</b>	Code de la langue à utiliser pour l'affichage (fr, en, ...)
	*/
	public function setLangue($langue)
	{
		$this->langue = $langue;
	}
	
	
	/**
	Charge un fichier de langue.
	
	Exemples:
	
	(on suppose que la langue est 'fr')
	
	1.
	$core->chargerFichierLangue('date');
	Charge le fichier 'langues/fr/date.lang.php'
	
	2.
	$core->chargerFichierLangue('sondage', CP_CHEMIN_PLUGINS.'/sondage/langues');
	Charge le fichier CP_CHEMIN_PLUGIN.'/sondage/langues/fr/sondage.lang.php'

	@param	fichier	<b>string</b>	Nom du fichier de langue, sans extension.
	@param	chemin	<b>string</b>	Chemin pour accéder au dossier de langues qui contient le fichier, à partir de la racine de l'application
	*/
	public function chargerFichierLangue($fichier, $chemin='')
	{
		if(isset($this->langue)) {
			if(empty($chemin)) {
				$chemin = CP_CHEMIN_LANGUES.'/';
			}

			l10n::set(path::clean($chemin.'/'.$this->langue.'/'.$fichier));
		}
	}
	
	
	/**
	Renvoie un tableau contenant les informations de chaque langue installée.
	
	@return	<b>array</b>	Langues
	*/
	public function getLangues()
	{
		$this->chargerLangues();
		return $this->langues;
	}
	
	
	/**
	Retourne un <b>array</b> contenant une liste de toutes les
	langues installées prêt à être utilisé par 'form::combo'.
	
	@return	<b>array</b>
	*/
	public function getComboLangues()
	{
		$languesCombo = array();
		foreach($this->getLangues() as $v) {
			$languesCombo[$v['nom']] = $v['identifiant'];
		}
		return $languesCombo;	
	}	
	//@}
	
	
	
	
	
	/// @name Générateurs de pages
	//@{
	
	
	/**
	Initialise un générateur de page. Cette méthode doit être appelée
	dans le fichier '_prepend.php' d'un plugin.
	*/
	public function initialiserGenerateur($identifiant, $nom, $description, $dossier)
	{
		
		global $__autoload;
		$nomClasse = 'generateur'.$identifiant;
		$__autoload[$nomClasse] = path::clean($dossier.'/page.php');
		
		$this->generateurs[$identifiant] = array(
			'identifiant' 	=> $identifiant,
			'nom'			=> $nom,
			'description'	=> $description,
			'dossier'		=> $dossier
		);
	}

	
	/**
	Renvoie un tableau contenant les informations des générateurs de
	page disponibles.
	
	@return	<b>array</b>	Générateurs de page
	*/
	public function getGenerateurs()
	{
		return $this->generateurs;		
	}
	//@}
	
	
	
	
	
	/// @name Filtres antispam
	//@{


	/**
	Initialise un filtre antispam. Cette méthode doit être appelée
	dans le fichier '_prepend.php' d'un plugin.
	*/
	public function initialiserAntispam($identifiant, $nom, $description, $dossier)
	{
		$this->filtresAntispam[$identifiant] = array(
			'identifiant' 	=> $identifiant,
			'nom'			=> $nom,
			'description'	=> $description,
			'dossier'		=> $dossier
		);
	}
	

	/**
	Vérifie si un commentaire est indésirable.
	
	@param	auteur	<b>string</b>	Auteur du commentaire
	@param	email	<b>string</b>	Email de l'auteur du commentaire
	@param	ip		<b>string</b>	Adresse IP de l'auteur du commentaire
	@param	titre	<b>string</b>	Titre du commentaire
	@param	contenu	<b>string</b>	Contenu du commentaire
	@param	page_id	<b>string</b>	Identifiant de la page associée au commentaire
	@param	details	<b>array</b>	Si le commentaire est indésirable, la méthode placera dans ce
		tableau des détails concernant le filtrage.
		
	@return	<b>boolean</b>
	*/
	public function estIndesirable($auteur, $email, $ip, $titre, $contenu, $page_id, &$details)
	{
		global $__autoload;
		
		$actifs = $this->site->getPropriete('antispams');
		$actifs = unserialize($actifs);
		
		$details = array();

		foreach($this->filtresAntispam as $k=>$v)
		{
			if($actifs[$k] != true) {
				continue;
			}
			
			$nomClasse = 'antispam'.$k;
			$__autoload[$nomClasse] = path::clean($v['dossier'].'/antispam.php');
			$antispam = new $nomClasse($this, $k);
			$tDetails = NULL;
			$estIndesirable = $antispam->estIndesirable($auteur, $email, $ip, $titre, $contenu, $page_id, $tDetails);
			if($estIndesirable == true) {
				$details[$k] = $tDetails;
				return true;
			}
		}
		return false;
	}
	//@}





	/// @name Gadgets
	//@{


	/**
	Initialise un gadget. Cette méthode doit être appelée
	dans le fichier '_prepend.php' d'un plugin.
	*/
	public function initialiserGadget($identifiant, $nom, $description, $dossier)
	{
		$this->gadgets[$identifiant] = array(
			'identifiant' 	=> $identifiant,
			'nom'			=> $nom,
			'description'	=> $description,
			'dossier'		=> $dossier
		);
	}
	
	
	/**
	Initialise une instance de la classe associée à un gadget.
	
	@param	gadget_id	<b>string</b>		Identifiant du gadget
	@param	rs			<b>recordset</b>	Informations du gadget
		
	@return	<b>object</b>
	*/
	public function getObjetGadget($gadget_id, $rs = null)
	{
		
		global $__autoload;
		
		if(!file_exists($this->gadgets[$gadget_id]['dossier'].'/gadget.php')) {
			throw new cpErreur(sprintf(__('Le gadget %s n\'a pas pu être chargé.').' '.__('Il a peut-être été désintallé.'), html::escapeHTML($gadget_id)), null, 601);
		}
		
		$nomClasse = 'gadget'.path::clean($gadget_id);
		
		$__autoload[$nomClasse] = $this->gadgets[$gadget_id]['dossier'].'/gadget.php';
		
		return new $nomClasse($this, $gadget_id, $rs);
		
	}
	//@}
	
	
	
	

	/// @name Thèmes
	//@{
	
	
	/**
	Charge les thèmes.
	*/
	public function chargerThemes()
	{

		if(count($this->themes) == 0)
		{
		
			$themes = $this->con->select(
				'SELECT composant,version '.
				'FROM '.$this->prefix.'version '.
				"WHERE type = 'theme'");
			
			$dossierThemes = path::real(CP_CHEMIN_THEMES.'/');
			
			if (($d = @dir($dossierThemes)) === false) {
				return false;
			}
			
			while($themes->fetch())
			{
				
				$dossierTheme = $dossierThemes.'/'.$themes->composant;
				
				if(is_dir($dossierTheme) && 
				   file_exists($dossierTheme.'/description.php') &&
				   !file_exists($dossierTheme.'/_desactive') )
				{	
					$this->dossier = $dossierTheme;
					$this->identifiant = $themes->composant;
					require $dossierTheme.'/description.php';
					$this->dossier = NULL;
					$this->identifiant = NULL;
				}
				
			}
			
		}
		
	}
	
	
	/**
	Initialise un thème. Cette méthode doit être appelée
	dans le fichier '_prepend.php' d'un plugin.
	*/
	public function initialiserTheme($nom, $description, $auteur, $version, $versionCp)
	{
		
		$this->themes[$this->identifiant] = array(
			'chemin' => $this->dossier,
			'identifiant' => $this->identifiant,
			'nom' => $nom,
			'description' => $description,
			'auteur' => $auteur,
			'version' => $version,
			'versionCp'		=> $versionCp
		);
		
	}
	
	
	/**
	Installe les thèmes.
	*/
	public function installerThemes()
	{
		
		global $__autoload;
		
		$res = array(
			'succes'	=> array(),
			'echec'		=> array()
		);
		
		$dossierThemes = path::real(CP_CHEMIN_THEMES.'/');
		
		if (($d = @dir($dossierThemes)) === false) {
			return false;
		}
		
		$themes = $this->con->select(
			'SELECT composant,version '.
			'FROM '.$this->prefix.'version '.
			"WHERE type = 'theme'");
		
		$themesDB = array();
		while($themes->fetch()) {
			$themesDB[$themes->composant] = $themes->version;
		}
		
		while (($idTheme = $d->read()) !== false)
		{
			
			$dossierTheme = $dossierThemes.'/'.$idTheme;
			
			if ($idTheme != '.' && $idTheme != '..' && is_dir($dossierTheme)
				&& file_exists($dossierTheme.'/description.php')
				&& !file_exists($dossierTheme.'/_desactive') )
			{

				$msg = '';
				
				# Inclusion du fichier de description
				$this->dossier = null;
				$this->identifiant = 'temp';
				$this->version = null;
				require $dossierTheme.'/description.php';
				
				if(is_array($this->themes['temp']['versionCp']) && (version_compare($this->themes['temp']['versionCp'][0], CP_VERSION,'>') || version_compare($this->themes['temp']['versionCp'][1], CP_VERSION,'<')))
				{
					$i = false;
					$msg = sprintf(__('Le module %s nécessite une version de Clicpage comprise entre %s et %s pour fonctionner adéquatement.'),
						$idTheme, $this->themes['temp']['versionCp'][0], $this->themes['temp']['versionCp'][1]);
				}
				elseif(!empty($this->themes['temp']['versionCp']) && version_compare($this->themes['temp']['versionCp'], CP_VERSION,'>'))
				{
					$i = false;
					$msg = sprintf(__('Le module %s nécessite Clicpage %s ou ultérieur pour fonctionner adéquatement.'),
						$idTheme, $this->themes['temp']['versionCp']);
				}
				else
				{
					
					$className = 'theme'.$idTheme;
					$__autoload[$className] = $dossierTheme.'/theme.php';
					
					# Première installation
					if(!isset($themesDB[$idTheme]))
					{
						$i = true;
						$themeObj = new $className($this);
						$themeObj->gadgetsDefaut();
						$this->setVersion('theme', $idTheme, $this->themes['temp']['version']);
					}
					# Mise à jour
					elseif(version_compare($themesDB[$idTheme], $this->themes['temp']['version'], '<') == 1)
					{
						$i = true;
						$this->setVersion('theme', $idTheme, $this->themes['temp']['version']);
					}
					else
					{
						$i = null;
					}
					
				}

				# Résultat de l'installation
				if($i === true) {
					$res['succes'][] = $idTheme;
				} elseif($i === false) {
					$res['echec'][] = array(
						$idTheme,
						$msg
					);
				}

			}
			
		}
		
		$this->themes = array();
		$this->chargerThemes();
		
		$d->close();
		
		return $res;

	}
	
	
	/**
	Retourne un <b>array</b> contenant une liste de tous les
	thèmes installés prêt à être utilisé par 'form::combo'.
	
	@return	<b>array</b>
	*/
	public function getComboThemes()
	{
		$this->chargerThemes();
		
		$comboThemes = array();
		
		foreach($this->themes as $theme)
		{
			$comboThemes[$theme['nom']] = $theme['identifiant'];
		}
		
		return $comboThemes;
		
	}
	
	
	/**
	Initialise une instance de la classe associée à un thème.
	
	@param	theme	<b>string</b>	Identifiant du thème
		
	@return	<b>object</b>
	*/
	public function getObjetTheme($theme)
	{
		
		global $__autoload;
		
		$this->chargerThemes();
		
		if(!file_exists($this->themes[$theme]['chemin'].'/theme.php')) {
			throw new cpErreur(sprintf(__('Le thème %s n\'a pas pu être chargé.').' '.__('Il a peut-être été désintallé.'), html::escapeHTML($theme)), null, 601);
		}
		
		$nomClasse = 'theme'.path::clean($theme);
		$__autoload[$nomClasse] = $this->themes[$theme]['chemin'].'/theme.php';

		return new $nomClasse($this);
	}
	
	
	/**
	Vide le dossier de cache des templates
	*/
	public function viderCacheTemplates()
	{
		if (is_dir(CP_CHEMIN_CACHE.'/cbtpl')) {
			files::deltree(CP_CHEMIN_CACHE.'/cbtpl');
		}
	}
	//@}
	
	
	
	
	
	/// @name Modules et services pour l'administration
	//@{
	
	
	/**
	Définit un module d'administration.
	
	@param	module		<b>string</b>	Nom du module d'administration
	@param	class		<b>string</b>	Nom de la classe du module d'administration
	@param	fichier		<b>string</b>	Fichier contenant la classe du module d'administration
	*/
	public function setModuleAdmin($module, $class, $fichier)
	{
		global $__autoload;
		$__autoload[$class] = $fichier;
		$this->modulesAdmin[$module] = $class;
	}
	
	
	/**
	Définit un service pour l'administration.
	
	@param	service		<b>string</b>	Nom du service
	@param	callback	<b>callback</b>	Fonction à appeler
	*/
	public function setServiceAdmin($module, $callback)
	{
		$this->servicesAdmin[$module] = $callback;
	}
	//@}
	
	
	
	/// @name Conditions pour les pages
	//@{
	
	
	/**
	Retourne les conditions de page dans un tableau prêt
	à être sérialisé.

	<var>$conditions</var> doit avoir la structure suivante:
	[1] => array( séparateurs )
	[2] => array( champs )
	[3] => array( opérateurs )
	[4] => array( valeurs )

	@param	conditions	<b>array</b>	Conditions
	@return	<b>array</b>
	*/
	public function parseConditionsPage($conditions, $acceptSelfCat=true)
	{
		
		$conditionsReturn = array();
		
		$max = count($conditions[1]);
		
		for($i=0; $i<$max; $i++)
		{
			
			# Vérification du séparateur
			if(!in_array($conditions[1][$i], array('and', 'or'))) {
				throw new cpErreur(__('Donnée invalide'), 'conditionsPage_separateur', 103);
			}
			
			# Vérification du champ
			if(!in_array($conditions[2][$i], array(
				'page_id',
				'page_titre',
				'tag',
				'page_url',
				'cat_id',
				'page_date',
				'page_date_modification',
				'utilisateur_id',
				'page_langue'
			))) {
				throw new cpErreur(__('Donnée invalide'), 'conditionsPage_champ', 103);
			}

			switch($conditions[2][$i]):
			
				case page_id:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!='))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}
					
					# Vérification de la valeur
					if(empty($conditions[4][$i])) {
						throw new cpErreur(__('Vous avez oublié d\'indiquer une page.'), 'conditionsPage_valeur', 101);
					}
					elseif(!is_numeric($conditions[4][$i])) {
						throw new cpErreur(__('Donnée invalide').' (page_id)', 'conditionsPage_valeur', 103);
					}
					
					$conditions[4][$i] = (integer) $conditions[4][$i]; # La valeur est transformée en integer.
					
				break;
				
				case page_titre:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!=', 'like', 'notLike'))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}

					# Vérification de la valeur
					if(empty($conditions[4][$i])) {
						throw new cpErreur(__('Vous avez oublié d\'indiquer un titre de page.'), 'conditionsPage_valeur', 101);
					}
					
				break;
				
				case tag:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!='))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}

					# Vérification de la valeur
					if(empty($conditions[4][$i])) {
						throw new cpErreur(__('Vous avez oublié d\'indiquer un tag.'), 'conditionsPage_valeur', 101);
					}
					
				break;
					
				case page_url:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!=', 'like', 'notLike'))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}

					# Vérification de la valeur
					if(empty($conditions[4][$i])) {
						throw new cpErreur(__('Vous avez oublié d\'indiquer une URL de page.'), 'conditionsPage_valeur', 101);
					}
					
				break;
				
				case cat_id:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!='))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}					
					
					# Vérification de la valeur
					if(empty($conditions[4][$i])) {
						throw new cpErreur(__('Vous avez oublié d\'indiquer un dossier.'), 'conditionsPage_valeur', 101);
					}
					elseif($acceptSelfCat && $conditions[4][$i] == '_self') {
						$conditions[4][$i] = 0;
					}
					elseif(!is_numeric($conditions[4][$i])) {
						throw new cpErreur(__('Donnée invalide').' (cat_id)', 'conditionsPage_valeur', 103);
					}
					
					$conditions[4][$i] = (integer) $conditions[4][$i]; # La valeur est transformée en integer.
					
				break;
				
				case page_date:
				case page_date_modification:
					
					# Vérification de l'opérateur
					if(in_array($conditions[3][$i], array('=', '!=', 'after', 'before'))) {
						
						# La valeur doit être une date valide
						if(   !( $valeur = strtotime($conditions[4][$i]) )   ) {
							throw new cpErreur(__('Date invalide'), 'conditionsPage_valeur', 104);
						}
						$conditions[4][$i] = $valeur;
						
					} else {
						
						# La valeur doit être numérique
						if(   !is_numeric($conditions[4][$i])   ) {
							throw new cpErreur(__('Nombre de jours ou d\'heures invalide'), 'conditionsPage_valeur', 103);
						}
				
					}		
					
				break;
				
				case utilisateur_id:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!='))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}
					
					# Vérification de la valeur
					if(empty($conditions[4][$i])) {
						throw new cpErreur(__('Vous avez oublié d\'indiquer un auteur.'), 'conditionsPage_valeur', 101);
					}
					elseif(!is_numeric($conditions[4][$i])) {
						throw new cpErreur(__('Donnée invalide').' (utilisateur_id)', 'conditionsPage_valeur', 103);
					}
					
					$conditions[4][$i] = (integer) $conditions[4][$i]; # La valeur est transformée en integer.
					
				break;
				
				case page_langue:
					
					# Vérification de l'opérateur
					if(!in_array($conditions[3][$i], array('=', '!='))) {
						throw new cpErreur(__('Donnée invalide').' (operateur)', 'conditionsPage_operateur', 103);
					}
					
					# Vérification de la valeur
					$this->chargerLangues();
					if(!in_array($conditions[4][$i], array_keys($this->langues))) {
						throw new cpErreur(__('Donnée invalide').' (page_langue)', 'conditionsPage_valeur', 103);
					}
					
					$conditions[4][$i] = $conditions[4][$i];
					
				break;
			
			endswitch;
			
			$conditionsReturn[] = array(
				1 => $conditions[1][$i],
				2 => $conditions[2][$i],
				3 => $conditions[3][$i],
				4 => $conditions[4][$i]
			);
			
		}
		
		return $conditionsReturn;
		
	}
	
	
	/**
	Produit un code Javascript décirvant les conditions de page
	et pouvant être utilisé par la fonction $( ).cpConditionsPage(nom, >valeurs<, acceptSelfCat).

	@param	conditions	<b>array</b>	Conditions
	
	@return	<b>string</b>
	*/
	public function conditionsPageToJs($conditions)
	{

		$conditionsVal = array();
		
		foreach($conditions as $condition)
		{
			# Timestamp -> date lisible
			if(	in_array($condition[2], array('page_date', 'page_date_modification'))	&&
				in_array($condition[3], array('=', '!=', 'after', 'before'))
			) {
				$condition[4] = dt::str('%d/%m/%Y', $condition[4]);
			}
			
			$conditionsVal[] =
				"new Array('".$condition[1]."','".$condition[2]."','".$condition[3]."','".html::escapeHTML($condition[4])."')";
		}
		
		return "new Array(\n".implode($conditionsVal, ",\n")."\n)";
		
	}
	
	
	/**
	Produit une partie de requête SQL correspondant aux conditions
	de page (à utiliser après le mot-clé «WHERE»).

	@param	conditions	<b>array</b>	Conditions
	@param	catActuelle	<b>integer</b>	Identifiant de la catégorie actuelle
	
	@return	<b>string</b>
	*/
	public function conditionsPageToSQL($conditions, $catActuelle=0)
	{
		
		$sql = '';
		$first = true;
		
		foreach($conditions as $condition)
		{
				# Séparateur
				if($first) {
					$first = false;
				} else {
					$sql .= $condition[1].' ';
				}
				
				# Champ
				if($condition[2] == 'cat_id' && $condition[3] == '=') {
					if($condition[4] == 0) {
							$condition[4] = $catActuelle;
					}
					$sql.= '(P.cat_id = '.(integer) $condition[4].' OR '.
					'P.page_id IN (SELECT home_page_id FROM '.$this->prefix.'categories WHERE parent_cat_id = '.(integer) $condition[4].') )';
				}
				elseif($condition[2] == 'tag')
				{
					if($condition[3] == '=') {
						$sql.=
						'P.page_id IN ( '.
							'SELECT page_id FROM '.$this->prefix.'tags WHERE '.
							'tag_texte = "'.$this->con->escape($condition[4]).'")';
					} else {
						$sql.=
						'P.page_id NOT IN ( '.
							'SELECT page_id FROM '.$this->prefix.'tags WHERE '.
							'tag_texte = "'.$this->con->escape($condition[4]).'")';
					}
				}
				else
				{
				
					$sql .= 'P.'.$condition[2].' ';
					
					# Opérateur
					switch($condition[3]):
					
						case notLike:
							$sql .= "NOT LIKE '".$this->con->escape($condition[4])."'";
							break;
					
						case after:
							$sql .= ">= '".$this->con->escape(dt::str('%Y-%m-%d', $condition[4]))."'";
							break;
							
						case before:
							$sql .= "<= '".$this->con->escape(dt::str('%Y-%m-%d', $condition[4]))."'";
							break;
							
						case il_y_a_plus_jours:
						case il_y_a_moins_jours:
							
							switch($condition[3]):
								case il_y_a_plus_jours:
									$sql .= " <= ";
									break;
								case il_y_a_moins_jours:
									$sql .= " >= ";
									break;
							endswitch;
											
							$sql .= "'".$this->con->escape(dt::str('%Y-%m-%d', strtotime('-'.$condition[4].' days')))."'";
							
							break;
							
						case il_y_a_plus_heures:
						case il_y_a_moins_heures:
							
							switch($condition[3]):
								case il_y_a_plus_heures:
									$sql .= " <= ";
									break;
								case il_y_a_moins_heures:
									$sql .= " >= ";
									break;
							endswitch;
											
							$sql .= "'".$this->con->escape(dt::str('%Y-%m-%d %H:%M', strtotime('-'.$condition[4].' hours')))."'";
							
							break;
							
						default:
							
							$reg = true;
							
							if(in_array(
								$condition[2],
								array('page_date_modification', 'page_date')
							)) {
								$condition[4] = dt::str('%Y-%m-%d', $condition[4]);
								
							} elseif($condition[2] == 'cat_id') {
								if($condition[4] == 0) {
									$condition[4] = $catActuelle;
								}
								
							}
							
							$sql .= $condition[3].' "'.$this->con->escape($condition[4]).'"';
							
							break;
							
					endswitch;
				
				}
				
				$sql .= "\n";
				
		}
		
		return $sql;
				
	}
	//@}
	
	
	
	
	
	/// @name Sécurité des formulaires
	//@{
	
	
	/**
	Retourne le code HTML d'un champ de formulaire masqué contenant
	un nonce.
	
	@param	action	<b>string</b>	Action associée au nonce
	
	@return	<b>string</b>	Champ de formulaire masqué contenant le nonce
	*/
	public function nonceHTML($action)
	{
		return form::hidden('nonce', $this->genererNonce($action));
	}
	
	
	/**
	Génère et retourne un nonce.
	
	@param	action	<b>string</b>	Action associée au nonce
	
	@return	<b>string</b>	nonce
	*/
	public function genererNonce($action)
	{		
		return $_SESSION['nonces'][$action] = sha1(uniqid(rand(),true));
	}
	
	
	/** @ignore */
	public function detruireNonce($action)
	{
		unset($_SESSION['nonces'][$action]);
	}
	
	
	/** @ignore */
	public function verifierNonce($action, $nonce)
	{
		
		if(!isset($_SESSION['nonces'][$action]) || $_SESSION['nonces'][$action] != $nonce) {
			return false;
		}

		return true;
	}
	
	
	/**
	Retourne le nonce à utiliser pour les requêtes ajax
	
	@return	<b>string</b>
	*/
	public function getAjaxNonce()
	{
		if(empty($_SESSION['nonces']['ajax'])) {
			return $this->genererNonce('ajax');
		}
		return $_SESSION['nonces']['ajax'];
		
	}
	//@}
	
	
}

?>